package at.jku.ssw.fortress.ui;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.viewers.IPostSelectionProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditor;
import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
import org.eclipse.ui.texteditor.TextOperationAction;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;

import sun.misc.Perf.GetPerfAction;

import at.jku.ssw.fortress.ui.outline.Definition;
import at.jku.ssw.fortress.ui.outline.FortressContentOutlinePage;
import at.jku.ssw.fortress.util.ColorManager;
import at.jku.ssw.fortress.util.Logger;

import com.sun.fortress.nodes.Node;

public class Editor extends AbstractDecoratedTextEditor {

	public static final String MATCHING_BRACKETS = "matchingBrackets";

	public static final String MATCHING_BRACKETS_COLOR = "matchingBracketsColor";

	private ColorManager colorManager;

	private ISelectionChangedListener outlineSelectionChangedListener = new ISelectionChangedListener() {
		public void selectionChanged(SelectionChangedEvent event) {
			IStructuredSelection selection = (IStructuredSelection) event
					.getSelection();
			if (selection.isEmpty()) {
				Editor.this.resetHighlightRange();
			} else {
				Object element = selection.getFirstElement();
				Assert.isTrue(element instanceof Definition);
				Definition def = (Definition) element;
				Position pos = def.getPosition();
				try {
					Editor.this.setHighlightRange(pos.getOffset(), pos
							.getLength(), true);
				} catch (IllegalArgumentException x) {
					Editor.this.resetHighlightRange();
				}
			}
		}
	};

	public Editor() {
		colorManager = new ColorManager();
		setDocumentProvider(new DocumentProvider());
		setSourceViewerConfiguration(new Configuration(this, colorManager));
		initializeDefaultValues();
	}

	private void initializeDefaultValues() {
		IPreferenceStore store = getPreferenceStore();
		store.setDefault(MATCHING_BRACKETS, true);
		PreferenceConverter.setDefault(store, MATCHING_BRACKETS_COLOR, new RGB(
				192, 192, 192));
	}

	@Override
	public void createPartControl(Composite parent) {
		super.createPartControl(parent);
		ISelectionChangedListener l = new ISelectionChangedListener() {
			public void selectionChanged(SelectionChangedEvent event) {
				Editor.this.selectionChanged();
			}
		};
		ISelectionProvider selectionProvider = getSelectionProvider();
		if (selectionProvider instanceof IPostSelectionProvider) {
			IPostSelectionProvider provider = (IPostSelectionProvider) selectionProvider;
			provider.addPostSelectionChangedListener(l);
		} else {
			selectionProvider.addSelectionChangedListener(l);
		}
	}

	@Override
	public void dispose() {
		colorManager.dispose();
		super.dispose();
	}

	@Override
	protected void createActions() {
		super.createActions();
		ResourceBundle bundle = FortressPlugin.getDefault().getResourceBundle();

		setAction("ContentFormatProposal", new TextOperationAction(bundle,
				"ContentFormatProposal.", this, ISourceViewer.FORMAT));

		IAction action = new TextOperationAction(bundle,
				"ContentAssistProposal.", this,
				ISourceViewer.CONTENTASSIST_PROPOSALS);
		action
				.setActionDefinitionId(ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS);
		setAction("ContentAssist.", action);
		markAsStateDependentAction("ContentAssist.", true);
	}

	@Override
	protected void performSave(boolean overwrite,
			final IProgressMonitor progressMonitor) {
		try {
			super.performSave(overwrite, progressMonitor);
			updateModel();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private FortressContentOutlinePage fOutlinePage;

	@Override
	public Object getAdapter(Class required) {
		if (IContentOutlinePage.class.equals(required)) {
			if (fOutlinePage == null) {
				fOutlinePage = new FortressContentOutlinePage(this);
				fOutlinePage
						.addSelectionChangedListener(outlineSelectionChangedListener);
			}
			return fOutlinePage;
		}
		return super.getAdapter(required);
	}

	public void selectionChanged() {
		ISelection s = getSelectionProvider().getSelection();
		if ((fOutlinePage != null) && (s instanceof ITextSelection)) {
			ITextSelection ts = (ITextSelection) s;
			fOutlinePage
					.removeSelectionChangedListener(outlineSelectionChangedListener);
			fOutlinePage
					.select(computeHighlightRangeDefinition(ts.getOffset()));
			fOutlinePage
					.addSelectionChangedListener(outlineSelectionChangedListener);
		}
	}

	private Definition computeHighlightRangeDefinition(int offset) {
		Definition model = getAbstractModel();
		if (model != null) {
			return model.find(offset);
		}
		return null;
	}

	private Node model;
	private Definition abstractModel;
	private boolean damaged = false;

	private List<ModelListener> modelListeners = new ArrayList<ModelListener>();

	public void addModelListener(ModelListener l) {
		if (l != null) {
			modelListeners.add(l);
		}
	}

	public void removeModelListener(ModelListener l) {
		if (l != null) {
			modelListeners.remove(l);
		}
	}
	
	public void updateModel() {
		model = null;
		abstractModel = null;
		damaged = false;
		for (ModelListener m : modelListeners) {
			m.modelChanged();
		}
	}

	public Node getModel() {
		IPreferenceStore store = FortressPlugin.getDefault().getPreferenceStore();
		String fortressHome = store.getString(FortressPlugin.FORTRESS_HOME_PREF);

		System.err.println("FORTRESS_HOME='" + fortressHome + "'");
		if(fortressHome == null || "".equals(fortressHome)) {
			Logger.logError("FORTRESS_HOME is not set", null);
			return null;
		}
		File file = new File(fortressHome);
		if(!file.exists()) {
			Logger.logError("FORTRESS_HOME does not exist", null);
			return null;
		}
		if(!file.isDirectory()) {
			Logger.logError("FORTRESS_HOME is not a directory", null);
			return null;
		}
		File sub = new File(fortressHome, "ProjectFortress");
		if(!sub.exists() || !sub.isDirectory()) {
			Logger.logError("FORTRESS_HOME must be set to the parent directory of 'ProjectFortress'", null);
			return null;
		}
		System.setProperty("fortress.autohome", fortressHome);
		
		if (model == null && !damaged) {
			IDocument inp = getDocumentProvider().getDocument(getEditorInput());
			String code = inp.get();
			model = ParserDriver.getAST(code, getEditorInput().getName());
			if(model == null) {
				damaged = true;
			}
		}
		return model;
	}

	public Definition getAbstractModel() {
		if (abstractModel == null) {
			Node ast = getModel();
			if (ast != null) {
				AbstractModelExtractor ame = new AbstractModelExtractor();
				abstractModel = ast.accept(ame);
			}
		}
		return abstractModel;
	}

}
